package com.d4a5.mapping.google2
{
	import com.d4a5.mapping.core.Bounds;
	import com.d4a5.mapping.core.MapObjectStateContainer;
	import com.d4a5.mapping.core.MinMax;
	import com.d4a5.mapping.core.StateArray;
	import com.d4a5.mapping.core.Utils;
	import com.d4a5.mapping.google.wmgArcgisDynamicMapServiceLayer;
	import com.d4a5.mapping.google.wmgRenderer;
	import com.google.maps.LatLng;
	import com.google.maps.LatLngBounds;
	import com.google.maps.MapMouseEvent;
	import com.google.maps.PaneId;
	import com.google.maps.interfaces.IMap;
	import com.google.maps.interfaces.IOverlay;
	import com.google.maps.interfaces.IPane;
	import com.google.maps.overlays.OverlayBase;
	
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.events.IEventDispatcher;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	import flash.utils.getQualifiedClassName;
	
	
	public class wmgOverlayLayer extends wmgOverlayBase implements IOverlay
	{
		
		protected const HALFSIZE_OF_EARTH:Number = 20037508.342789247;
		public static var currentZoomLevel:int = -1;
		protected static var currentCenterLatLng:LatLng;
		protected static var currentMatrix:Matrix;
		
		protected var _highlightable:Boolean = false;
		protected var _selectable:Boolean = false;
		
		public static var currentMapBounds:Bounds;
		protected static var currentViewportBounds:Bounds;
		protected static var currentMapLatLngBounds:LatLngBounds;
		
		protected var _lockObj:Object;
		
		public var optionsManager:wmgOverlayLayerOptions;
	
		
		//protected var _renderer:wmgOverlayLayerRenderer;
		
		public var highlighted:StateArray;
		public var selected:StateArray;
		protected var _invalidProperties:Boolean = false;
		
		public function wmgOverlayLayer()
		{
			super();
			
			
			this.optionsManager = new wmgOverlayLayerOptions();
			this.optionsManager.addEventListener("redraw_requested",onRedrawRequested);
			this.optionsManager.addEventListener("state_options_changed",onStateOptionsChanged);
			
			
			this.highlighted = new StateArray( "highlighted" );
			this.selected = new StateArray( "selected" );
			
			this.addEventListener( Event.ENTER_FRAME , onEnterFrame );
		}
		
		protected function onLayerAddedToStage( event:Event ) : void {
			this.removeEventListener(Event.ADDED_TO_STAGE,onLayerAddedToStage);
			
			if( this.stage && this._invalidProperties ){
				this._invalidProperties = false;
				this.invalidateProperties();
			}
		}
		
		public function invalidateProperties() : void {
			if( this.stage ){
				this.stage.addEventListener(Event.ENTER_FRAME,onNextFrameCommitProperties);
			}
			else{
				this._invalidProperties = true;
				this.addEventListener(Event.ADDED_TO_STAGE,onLayerAddedToStage);
			}
		}
		
		protected function onNextFrameCommitProperties( event:Event ) : void {
			this.stage.removeEventListener(Event.ENTER_FRAME,onNextFrameCommitProperties);
			this.commitProperties();
		}
		
		protected function commitProperties() : void {
			
		}
		
		
		protected function onEnterFrame( event:Event ) : void {
			if( this.pane != null && this.pane.map != null ){
				this.removeEventListener( Event.ENTER_FRAME , onEnterFrame );
			
				if( this._highlightable ){
					this.pane.map.addEventListener( MapMouseEvent.MOUSE_MOVE , onMouseMove );
				}
				if( this._selectable ){
					this.pane.map.addEventListener( MapMouseEvent.CLICK , onMouseClick );
				}
			}
		}
		
		override public function positionOverlay(arg0:Boolean):void {
			
			if( this.pane != null ){
				if( this._lockObj == null ){
					var m:Matrix = this.getMatrix();
					if( m ){
						
						
						
						var start:Date = new Date();
						
						var i:int = 0;
						var numShapes:int = this._shapes.length;
						var shape:wmgOverlayLayerBaseShape;
						if( arg0 ){
							for( ; i < numShapes; i++ ){
								(this._shapes[i] as wmgOverlayLayerBaseShape).redraw( m );
							}
						}
						else{
							
							var xStart:Number = m.tx;
							var yStart:Number = m.ty;
							
							var mapBoundsX:MinMax = wmgOverlayLayer.currentMapBounds.x;
							if( mapBoundsX.min > -this.HALFSIZE_OF_EARTH && mapBoundsX.max < this.HALFSIZE_OF_EARTH ){
								for( ; i < numShapes; i++ ){
									shape = this._shapes[i] as wmgOverlayLayerBaseShape;
									shape.y = yStart;
									shape.x = xStart;
								}
							}
							else{
								var mapWidthPixels:Number = ( 1 << (wmgOverlayLayer.currentZoomLevel + 8) );
								var xMax:Number = wmgOverlayLayer.currentMapBounds.x.max;
								var xMin:Number = wmgOverlayLayer.currentMapBounds.x.min;
								var plus:Number =  m.tx + mapWidthPixels;
								var minus:Number =  m.tx - mapWidthPixels;
								var xMinMax:MinMax;
								
								for( ; i < numShapes; i++ ){
									shape = this._shapes[i] as wmgOverlayLayerBaseShape;
									shape.y = yStart;
									xMinMax = shape.amf.bounds.x;
									shape.x =( xMinMax.min > xMax) 
										? minus
										: ( xMinMax.max < xMin )
											? plus
											: xStart;
								}
							}
						}
						
						
						
					
						var stop:Date = new Date();
						trace( "refreshOrRedraw" + ":" + (stop.time-start.time).toString() );
						//trace( ((new Date()).time).toString() );
					}
				}
			}
		}
		
		public function generalizeAllShapesUsingCurrentMap( generalizePixelCount:uint = 2) : void {
			if( this.pane != null ){
				if( this._lockObj == null ){
					var m:Matrix = this.getMatrix();
					if( m ){
						var numShapes:int = this._shapes.length;
						for( var i:int = 0 ; i < numShapes; i++ ){
							this._shapes[i].generalizeAmf( m , generalizePixelCount );
						}
					}
				}
			}
		}
		
		
		override public function getDefaultPane(arg0:IMap):IPane{
			return arg0.getPaneManager().getPaneById(PaneId.PANE_OVERLAYS);
		}
		
		protected function onRedrawRequested( event:Event ) : void {
			this.positionOverlay(true);
		}
		
		
		protected function onStateOptionsChanged( event:Event ) : void {
			this.checkStates();
		}
		
		protected function checkStates() : void {
			this.highlightable = ( this.optionsManager.highlightedOptions || this.optionsManager.bothOptions );
			this.selectable = ( this.optionsManager.selectedOptions || this.optionsManager.bothOptions );
		}
		
		
		public function get highlightable() : Boolean {
			return this._highlightable;
		}
		public function set highlightable( state:Boolean ) :void {
			if( state != this._highlightable ){
				this._highlightable = state;
				if( this.pane && this.pane.map ){
					if( this._highlightable ){
						this.pane.map.addEventListener( MapMouseEvent.MOUSE_MOVE , onMouseMove );
					}
					else{
						this.pane.map.removeEventListener( MapMouseEvent.MOUSE_MOVE , onMouseMove );
					}
				}
			}
		}
		
		
		public function get selectable() : Boolean {
			return this._selectable;
		}
		public function set selectable( state:Boolean ) :void {
			if( state != this._selectable ){
				this._selectable = state;
				if( this.pane && this.pane.map ){
					if( this._selectable ){
						this.pane.map.addEventListener(MapMouseEvent.CLICK,onMouseClick);
					}
					else{
						this.pane.map.removeEventListener(MapMouseEvent.CLICK,onMouseClick);
					}
				}
			}
		}
		
		
		
		
		public function add( shape:wmgOverlayLayerBaseShape ) : void {
			this.addChild( shape );
			
			shape.addEventListener( "highlighted_true" , this.highlighted.trueHandler );
			shape.addEventListener( "highlighted_false" , this.highlighted.falseHandler );
			shape.addEventListener( "selected_true" , this.selected.trueHandler );
			shape.addEventListener( "selected_false" , this.selected.falseHandler );
			
			
			this.optionsManager.add( shape );
			
			shape.redraw( this.getMatrix() );
		}
		
		public function remove( shape:wmgOverlayLayerBaseShape ) : void {
			var index:int = this.getChildIndex( shape );
			if( index >= 0 ){
				var shape:wmgOverlayLayerBaseShape = this.removeChildAt( index ) as wmgOverlayLayerBaseShape;
				
				shape.removeEventListener( "highlighted_true" , this.highlighted.trueHandler );
				shape.removeEventListener( "highlighted_false" , this.highlighted.falseHandler );
				shape.removeEventListener( "selected_true" , this.selected.trueHandler );
				shape.removeEventListener( "selected_false" , this.selected.falseHandler );
				
				this.optionsManager.add( shape );
			}
		}
		public function removeAll() : void {
			var numShapes:int = this.numChildren;
			for( var index:int = numShapes-1; index >= 0; index-- ){
				var shape:wmgOverlayLayerBaseShape = this.removeChildAt( index ) as wmgOverlayLayerBaseShape;
				
				shape.removeEventListener( "highlighted_true" , this.highlighted.trueHandler );
				shape.removeEventListener( "highlighted_false" , this.highlighted.falseHandler );
				shape.removeEventListener( "selected_true" , this.selected.trueHandler );
				shape.removeEventListener( "selected_false" , this.selected.falseHandler );
				
				this.optionsManager.remove( shape );
			}
		}
		
		
		public function get numShapes() : int {
			return this.numChildren;
		}
		
		public function getShapeAt( index:int ) : wmgOverlayLayerBaseShape {
			return this.getChildAt( index ) as wmgOverlayLayerBaseShape;
		}
		
		public function getMatrix() : Matrix {
			if( this.pane && this.pane.map ){
				var zoom:int = this.pane.map.getZoom();
				var center:LatLng = this.pane.map.getCenter();
				var mapLatLngBounds:LatLngBounds = this.pane.map.getLatLngBounds();
				
				if( zoom != wmgOverlayLayer.currentZoomLevel || center.equals( wmgOverlayLayer.currentCenterLatLng ) == false || mapLatLngBounds.equals( wmgOverlayLayer.currentMapLatLngBounds ) == false  ){
					var viewport:Rectangle = this.pane.getViewportBounds();
					var mapWidthPixels:Number = ( 1 << (zoom + 8) );
					var resolution:Number = 40075016.685578494 / mapWidthPixels;
					
					var UL:Point = this.pane.fromPaneCoordsToProjectionPoint( viewport.topLeft );
					var LR:Point = this.pane.fromPaneCoordsToProjectionPoint( viewport.bottomRight );
					
					mapWidthPixels >>= 1;
					
					var xmin:Number =  resolution * ( viewport.left   + UL.x - mapWidthPixels );
					var xmax:Number =  resolution * ( viewport.right  + UL.x - mapWidthPixels );
					var ymin:Number = -resolution * ( viewport.bottom + UL.y - mapWidthPixels );
					var ymax:Number = -resolution * ( viewport.top    + UL.y - mapWidthPixels );
					
					
					wmgOverlayLayer.currentZoomLevel = zoom;
					wmgOverlayLayer.currentCenterLatLng = center;
					wmgOverlayLayer.currentMapBounds = new Bounds( new MinMax(xmin,xmax) , new MinMax(ymin,ymax) );
					wmgOverlayLayer.currentMatrix = new Matrix( 1/resolution , 0 , 0 , -1/resolution , -UL.x + mapWidthPixels , -UL.y  + mapWidthPixels );
					wmgOverlayLayer.currentMapLatLngBounds = mapLatLngBounds;
					
					wmgOverlayLayer.currentViewportBounds = wmgOverlayLayer.currentMapBounds.applyMatrix( wmgOverlayLayer.currentMatrix );
				}
			}
			return wmgOverlayLayer.currentMatrix;
		} 
		
		public function delayRedrawUntilComplete( ied:IEventDispatcher , type:String , expectedTime:Number = Number.NaN ) : void {
			if( this._lockObj ){
				this._lockObj.safety.stop();
				this._lockObj.dispatcher.removeEventListener( this._lockObj.type , onDelayRedrawUntilComplete );
				
				this._lockObj.dispatcher = ied;
				this._lockObj.type = type;
			}
			else{
				var safety:Timer = new Timer(1,0);
				if( isNaN( expectedTime ) == false ){
					safety.delay = expectedTime;
				}
				safety.addEventListener(TimerEvent.TIMER,onDelayRedrawUntilComplete);
				this._lockObj = { dispatcher : ied , type : type , state : 1 , safety : safety };
			}
			if( isNaN( expectedTime ) == false ){
				this._lockObj.safety.start();
			}
			this._lockObj.dispatcher.addEventListener( this._lockObj.type , onDelayRedrawUntilComplete );
			this.visible = false;
		}
		
		protected function onDelayRedrawUntilComplete( event:Event ) : void {
			if( this._lockObj ){
				this._lockObj.safety.stop();
				this._lockObj.dispatcher.removeEventListener( this._lockObj.type , onDelayRedrawUntilComplete );
				this._lockObj = null;
				this.positionOverlay( true );
				this.visible = true;
			}
			
		}
		
		
		protected function onMouseClick( event:MapMouseEvent ) : void {
			this.defaultMouseClickSelection( event.latLng );
		}
		
		protected function defaultMouseClickSelection( ll:LatLng ) : void {
			var mouseX:Number = this.stage.mouseX;
			var mouseY:Number = this.stage.mouseY;
			
			var wm:Point = Utils.GeographicToWebMercator( ll.lat() , ll.lng() );
			var wmX:Number = wm.x;
			var wmY:Number = wm.y;
			
			var spIdx:Object = Utils.createXYSpatialIndex( wm.x , wm.y );
			var spIdxX:uint = spIdx.x;
			var spIdxY:uint = spIdx.y;
			
			var allowMultiples:Boolean = this.selected.allowMultiples;
			var i:int = 0;
			var shape:Object;
			var bounds:Object;
			if( this.selected.allowMultiples ){
				var hits:Array = new Array();
				for( ; i < this.numChildren; i++ ){
					shape = this._shapes[i];
					bounds = shape.amf.bounds;
					if((bounds.x.spatialIndex & spIdxX) > 0 && (bounds.y.spatialIndex & spIdxY) > 0 && shape.hitTestPoint( mouseX , mouseY , true ) ){
						if( shape.selected ){
							shape.selected = false;
							this.setChildIndex( shape as DisplayObject  , 0 );
						}
						else{
							shape.selected = true;
							hits.push(shape);
						}
					}
				}
				
				for( i = 0; i < hits.length; i++ ){
					this.setChildIndex( hits[i] as wmgOverlayLayerBaseShape , this.numChildren-1 );
				}
			}
			else{
				for( ; i < this.numChildren; i++ ){
					shape = this._shapes[i];
					bounds = shape.amf.bounds;
					shape.selected = ((bounds.x.spatialIndex & spIdxX) > 0 && (bounds.y.spatialIndex & spIdxY) > 0 )
						? shape.hitTestPoint( mouseX , mouseY , true )
						: false;
				}
			}
		}
		
		
		protected function onMouseMove( event:MapMouseEvent ) : void {
			//var start:Date = new Date();
			this.nearestMouseMoveHighlighter( event.latLng );
			//this.defaultMouseMoveHighlighter( event.latLng );
			//var stop:Date = new Date();
			//trace( (stop.time-start.time).toString() );
		}
		
		protected function defaultMouseMoveHighlighter( ll:LatLng ) : void {
			var wm:Point = Utils.GeographicToWebMercator( ll.lat() , ll.lng() );
			var spIdx:Object = Utils.createXYSpatialIndex( wm.x , wm.y );
			
			var mouseX:Number = this.stage.mouseX;
			var mouseY:Number = this.stage.mouseY;
			var spIdxX:uint = spIdx.x;
			var spIdxY:uint = spIdx.y;
			
			for( var i:int = 0; i < this.numChildren; i++ ){
				var shape:Object = this._shapes[i];
				var bounds:Object = shape.amf.bounds;
				shape.highlighted = ( (bounds.x.spatialIndex & spIdxX) > 0 && (bounds.y.spatialIndex & spIdxY) > 0 )
					?( shape.hitTestPoint( mouseX , mouseY , true ) )
					: false;
			}
		}
		
		protected function nearestMouseMoveHighlighter( ll:LatLng ) : void {
			var mouseX:Number = this.stage.mouseX;
			var mouseY:Number = this.stage.mouseY;
			
			var wm:Point = Utils.GeographicToWebMercator( ll.lat() , ll.lng() );
			var wmX:Number = wm.x;
			var wmY:Number = wm.y;
			
			var spIdx:Object = Utils.createXYSpatialIndex( wm.x , wm.y );
			var spIdxX:uint = spIdx.x;
			var spIdxY:uint = spIdx.y;
			
			var nearestDelta:Number = Number.POSITIVE_INFINITY;
			var nearestShape:Object = null;
			
			for( var i:int = 0; i < this.numChildren; i++ ){
				var shape:Object = this._shapes[i];
				var bounds:Object = shape.amf.bounds;
				if( (bounds.x.spatialIndex & spIdxX) > 0 && (bounds.y.spatialIndex & spIdxY) > 0 ){
					if( shape.hitTestPoint( mouseX , mouseY , true ) ){
						var delta:Number = (bounds.center.x-wmX)*(bounds.center.x-wmX)+(bounds.center.y-wmY)*(bounds.center.y-wmY);
						if( delta < nearestDelta ){
							if( nearestShape && nearestShape.highlighted ){
								nearestShape.highlighted = false;
								this.setChildIndex( nearestShape  as DisplayObject , (nearestShape.selected) ? this.numChildren-1 : 0 );
							}
							nearestDelta = delta;
							nearestShape = shape;
						}
						else if( shape.highlighted ){
							shape.highlighted = false;
							this.setChildIndex( shape  as DisplayObject , (shape.selected) ? this.numChildren-1 : 0 );
						}
					}
					else if( shape.highlighted ){
						shape.highlighted = false;
						this.setChildIndex( shape  as DisplayObject, (shape.selected) ? this.numChildren-1 : 0 );
					}
				}
				else if( shape.highlighted ){
					shape.highlighted = false;
					this.setChildIndex( shape  as DisplayObject, (shape.selected) ? this.numChildren-1 : 0 );
				}

			}
			
			if( nearestShape ){
				nearestShape.highlighted = true;
				this.setChildIndex( nearestShape  as DisplayObject , this.numChildren-1 );
			}
		}
		
		
	}
}